Skip to content

fix(session): default an unstated spawn cwd to the user's home - #36

Merged
karngyan merged 1 commit into
mainfrom
fix/default-session-cwd
Aug 9, 2026
Merged

fix(session): default an unstated spawn cwd to the user's home#36
karngyan merged 1 commit into
mainfrom
fix/default-session-cwd

Conversation

@karngyan

@karngyan karngyan commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

The bug

A session spawned with no cwd opens at / on service-managed machines. Registry.Spawn handed opts.Cwd straight to cmd.Dir, so an empty cwd — what the UI's plain New-session button sends — made the shell inherit the daemon's own working directory. A daemon under launchd or systemd runs at / (neither unit in internal/service/unit.go sets a working directory), so every default session landed there; a daemon started from a terminal put them wherever it happened to be launched from, which is no better chosen.

The fix

In Registry.start, an empty opts.Cwd now resolves to os.UserHomeDir() — the default every terminal emulator and sshd picks, and the same call Revive already uses for a snapshot whose directory has vanished. The one resolved value feeds both cmd.Dir and the recorded Info.Cwd; the old code seeded the record from os.Getwd() instead, a recorded-vs-actual split this closes. When even home is unresolvable the empty string stands and the shell inherits — the old behaviour, kept as the floor rather than the default. ($HOME is set by launchd user agents and systemd user managers alike, so the resolution works on exactly the path the bug describes; unlike $SHELL, no user-database fallback is needed.)

Callers that state a cwd are untouched — they all pass a non-empty value:

  • group-heading spawns (spawnFromGroupclient.spawn({ cwd }))
  • restart with the exited session's cwd (terminal.tsx handleRestart)
  • revival from snapshot (Registry.Revive passes snap.Cwd, or home when it has vanished)

Tests

Two new tests in internal/session/registry_test.go, following cwd_test.go's poll-and-resolve pattern against the real child process:

  • TestSpawnDefaultsCwdToHome — empty cwd: the recorded Info.Cwd is $HOME and processCwd confirms the child actually sits there.
  • TestSpawnKeepsExplicitCwd — stated cwd (deliberately not $HOME): recorded and actual both stay put.

Evidence: make web relay then go test ./... — all packages ok (including internal/session 7.5s) — and go vet ./... clean.

🤖 Generated with Claude Code

A session spawned with no cwd handed the empty string to cmd.Dir, so the
shell inherited the daemon's own working directory. Under launchd or
systemd that directory is / — neither unit in internal/service sets one —
so every plain new session opened at /; a hand-started daemon put them
wherever it happened to be launched from, which is no better chosen.

Registry.start now resolves an empty opts.Cwd to os.UserHomeDir(), the
same call Revive already uses for a vanished directory, and feeds that
one resolved value to both cmd.Dir and the recorded Info.Cwd — the old
code seeded the record from os.Getwd() instead, a recorded-vs-actual
split this closes. When home is unresolvable the empty string stands and
the shell inherits, which is the old behaviour kept as the floor.

Callers that state a cwd — group-heading spawns, restart with the exited
session's cwd, revival from snapshot — are untouched; they all pass a
non-empty value. Two registry tests pin each side: empty cwd lands the
child in $HOME, a stated cwd is never contested by it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@karngyan
karngyan merged commit 7ebc885 into main Aug 9, 2026
1 check passed
@karngyan
karngyan deleted the fix/default-session-cwd branch August 9, 2026 20:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant